package rebalance;

import java.io.Serializable;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import remote.RemoteDistributedHashMap;

import node.NodeManager;
import log.Logger;
import messaging.InterNodeMessage;
import messaging.RebalanceUpdates;

/**
 * This class handles all the messages received in a actual node due to re-balancing
 * when a new node is missing.
 * 
 * @author iyer
 * @version Spring 2013
 *
 */
public class HandleRebalanceMessages
{
  /**
   * Nodemanager reference.
   */
  private NodeManager nodemanager;
  /**
   * Actual node number.
   */
  private int actual_node_number;
  /**
   * rebalance update key.
   */
  private int FAULT_TOLERANCE = 9;
  /**
   * Constructor for this class that takes a reference to node manager as a parameter.
   */
  public HandleRebalanceMessages(final NodeManager nodeman)
  {
    this.nodemanager = nodeman;
    this.actual_node_number = nodeman.actual_node_number;
  }
  /**
   * Landing point for rebalancing messages due to fault tolerance.
   */
  public void faultTolhandler(InterNodeMessage my_message)
  {
    if (my_message.getRebUpdate().getId())
    {
      Logger.out(this.nodemanager.actual_node_number +
                 " Message received from predecessor node " +
           my_message.getMy_originalsource_node() +
           " for fault tolerance during rebalancing.");
      this.messagefrompredecessor(my_message);
      
    }
    else
    {
      Logger.out(this.nodemanager.actual_node_number +
                 " Message received from successor node " + 
          my_message.getMy_originalsource_node() +
          " for data backup for fault tolerance during rebalancing.");
      this.messagefromsuccessor(my_message);
    }
  }
  /**
   * This method handles messages received from its successor when a rebalancing is
   * happening in a ring.
   */
  public void messagefromsuccessor(final InterNodeMessage my_message)
  {
    //put the new nodes to the duplicate stores.
    ArrayList<Integer> new_nodes = my_message.getRebUpdate().getnewnodeset();
    HashMap<Integer, HashMap<String, Serializable>> storage = 
        my_message.getRebUpdate().getStorageData();
    Iterator<Integer> itr = new_nodes.iterator();
    while (itr.hasNext())
    {
      int node_num = (int) itr.next();
      this.nodemanager.duplicatestores.successor_node_set.add(node_num);
      this.nodemanager.duplicatestores.storage.put(node_num,storage.get(node_num));
    }
  }
  /**
   * This method handles messages received from its predecessor when a 
   * rebalancing is happening in a ring
   * This method sends a message back to the caller with all the data.
   */
  public void messagefrompredecessor(final InterNodeMessage my_message)
  {
    this.nodemanager.predecessor_actual_node = my_message.getMy_originalsource_node();
    ArrayList<Integer> new_nodes = this.nodemanager.node_set;
    HashMap<Integer, HashMap<String, Serializable>> storage = 
        new HashMap<Integer, HashMap<String, Serializable>>(); 
    for (int i = 0; i < new_nodes.size(); i++)
    {
      int node_num = new_nodes.get(i);
      storage.put(node_num, (HashMap<String, Serializable>) this.nodemanager.node_stubs.get(node_num).my_map);
    }
    int dest_node = this.nodemanager.hashhelp.optimalNodeFind(this.actual_node_number, 
                                                              my_message.getMy_originalsource_node());
    RebalanceUpdates reb_mess = new RebalanceUpdates();
    reb_mess.setnewnodesets(new_nodes);
    reb_mess.setStorageData(storage);
    reb_mess.setpredsuccid(false);
    InterNodeMessage message = new InterNodeMessage(this.actual_node_number,
                                                    my_message.getMy_originalsource_node(),
                                                    this.actual_node_number,
                                                    dest_node, FAULT_TOLERANCE);
    message.setRebUpdate(reb_mess);
    String dest_node_str = this.nodemanager.rmi_NodeFind(dest_node);
    try
    {
      final RemoteDistributedHashMap to_send =
        (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_node_str);
      to_send.send(message);
    }
    catch (final RemoteException e)
    {
    }
    catch (InterruptedException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (NotBoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
